﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Xml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Attributes;
using Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Support;

namespace Microsoft.WowAddonStudio.FrameXml.Serialization
{
    [DebuggerDisplay("Name = {Name}, Virtual = {Virtual}")]
    public class InheritableObject : SerializationObject, INamedObject, IInheritableObject<InheritableObject>, INotifyPropertyChanged
    {
        private readonly PropertyBag properties;
        private InheritableObject inheritedObject;

        private string name;
        private bool @virtual;
        private string inherits;

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Initializes a new instance of the <see cref="InheritableObject"/> class.
		/// </summary>
        public InheritableObject()
        {
            // Create property bag and add event handler lambda
            properties = new PropertyBag(this);
            properties.PropertyChanged += (sender, e) => this.OnPropertyChanged(e);
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        [XmlAttribute("name")]
		[Description("This string represents name of an element for scripting. Frames defined via XML must be named if they are to be accessed by scripts, but for showing up in interface unnamed are ok. Names are case sensitive - means \"MyElement\" not same as \"myelement\".")]
        public string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    string oldName = name;
                    name = value;

                    // Check whether the layout frame is virtual
                    if (this.Virtual)
                    {
                        // Register the layout frame and pass in the old name
                        RegisterObject(oldName);
                    }
					
                    this.OnPropertyChanged(new PropertyChangedEventArgs("Name"));
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the <see cref="LayoutFrame"/> is virtual.
        /// </summary>
        [XmlAttribute("virtual")]
        [DefaultValue(false)]
        [Category("Layout")]
		[Description("If true element will not created, but used as template for other elements. Default false.")]
        public bool Virtual
        {
            get { return @virtual; }
            set
            {
                // Only deal with this if it is a change in the state
                if (@virtual != value)
                {
                    @virtual = value;

                    // Make sure we have a valid name
                    if (!String.IsNullOrEmpty(this.Name))
                    {
                        // Check whether the frame is now virtual or the other way around
                        if (@virtual)
                            RegisterObject();
                        else
                            UnregisterObject();
                    }

					this.OnPropertyChanged(new PropertyChangedEventArgs("Virtual"));
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether inheritance is enabled.
        /// </summary>
        /// <remarks>
        /// The value is propagated to all non-inherited children.
        /// </remarks>
        [XmlIgnore]
        [Browsable(false)]
        public virtual bool InheritanceEnabled
        {
            get { return properties.InheritanceEnabled; }
            set { properties.InheritanceEnabled = value; }
        }

        /// <summary>
        /// Determines whether this instance [can be inherited from] the specified inheritable object type.
        /// </summary>
        /// <param name="inheritableObjectType">Type of the inheritable object.</param>
        /// <returns>
        /// 	<c>true</c> if this instance [can be inherited from] the specified inheritable object type; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanBeInheritedFrom(Type inheritableObjectType)
        {
            return inheritableObjectType == this.GetType();
        }

        /// <summary>
        /// Gets or sets the name of a <see cref="LayoutFrame"/> the <see cref="LayoutFrame"/> inherits from.
        /// </summary>
        [XmlAttribute("inherits")]
		[InheritsList]
		[EditorBrowsable]
		[Description("Name of element to take properties from.")]
        public string Inherits
        {
            get { return inherits; }
            set
            {
                if (inherits != value)
                {
                    inherits = value;

                    // Check whether we have a valid name and a layout frame is already registered under the name
                    if (!String.IsNullOrEmpty(inherits) && FrameXmlDirectory.Default.IsRegistered(inherits))
                    {
                        this.inheritedObject = FrameXmlDirectory.Default.Resolve(inherits);
                        this.properties.InheritedPropertyBag = this.inheritedObject.Properties;
                    }
                    else
                    {
                        inheritedObject = null;
                        this.properties.InheritedPropertyBag = null;
                    }

                    this.OnPropertyChanged(new PropertyChangedEventArgs("Inherits"));
                    this.OnPropertyChanged(new PropertyChangedEventArgs("InheritedObject"));
                }
            }
        }

        /// <summary>
        /// Gets or sets layout frame that the layout frame inherits from.
        /// </summary>
        [XmlIgnore]
        [Browsable(false)]
        public InheritableObject InheritedObject
        {
            get { return inheritedObject; }
            set
            {
                inheritedObject = value;

                if (inheritedObject != null)
                {
                    this.inherits = inheritedObject.Name;
                    this.properties.InheritedPropertyBag = inheritedObject.Properties;
                }
                else
                {
                    this.inherits = null;
                    this.properties.InheritedPropertyBag = null;
                }

                this.OnPropertyChanged(new PropertyChangedEventArgs("Inherits"));
                this.OnPropertyChanged(new PropertyChangedEventArgs("InheritedObject"));
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance inherits from an object.
        /// </summary>
        [Browsable(false)]
        public bool HasInheritedObject
        {
            get { return this.InheritedObject != null; }
        }

		/// <summary>
		/// Determines whether the specified frame is inherited.
		/// </summary>
		/// <param name="frame">The frame.</param>
		/// <returns>
		/// 	<c>true</c> if the specified frame is inherited; otherwise, <c>false</c>.
		/// </returns>
		public bool IsInherited(InheritableObject frame)
		{
			if(frame == null) return false;
			bool result = false;
			InheritableList<Frame> elements = Properties.GetInheritedElements<Frame>("Frames");
			if (elements != null)
				result = elements.OfType<LayoutFrame>().Contains(frame as LayoutFrame);
			
			if (!result)
			{
				LayerList layerList = Properties.GetInheritedElements<Layer, LayerList>("Layers");
				if (layerList != null)
				{
					result = layerList.OfType<LayoutFrame>().Contains(frame as LayoutFrame);
					if (!result)
					{
						result = layerList.ContainsFrame(frame as LayeredLayoutFrame);
					}
				}
			}
			return result;
		}

    	/// <summary>
		/// Checks whether a property has an inherited value.
		/// </summary>
		/// <param name="propertyName">The name of the property.</param>
		/// <returns>True, if the property has an inherited value; false otherwise.</returns>
		public bool HasInheritedValue(string propertyName)
    	{
    	    if (propertyName == null)
    	        throw new ArgumentNullException("propertyName");

    	    return Properties.HasInheritedValue(propertyName);
    	}

        /// <summary>
        /// Gets the inherited value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns></returns>
        public T GetInheritedValue<T>(string propertyName)
        {
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");

            return Properties.GetInheritedValue<T>(propertyName);
        }

        /// <summary>
        /// Determines whether the specified property name has value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>
        /// 	<c>true</c> if the specified property name has value; otherwise, <c>false</c>.
        /// </returns>
        public bool HasValue(string propertyName)
        {
            return Properties.HasValue(propertyName);
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns></returns>
        public T GetValue<T>(string propertyName)
        {
            return Properties.GetValue<T>(propertyName);
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="value">The value.</param>
        public void SetValue<T>(string propertyName, T value)
        {
            Properties.SetValue(propertyName, value);
        }

        /// <summary>
        /// Gets the inheritable list.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TInheritableList">The type of the inheritable list.</typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns></returns>
        public TInheritableList GetInheritableList<T, TInheritableList>(string propertyName) where T : SerializationObject where TInheritableList : InheritableList<T>
        {
            return Properties.GetInheritableList<T, TInheritableList>(propertyName);
        }

        /// <summary>
        /// Gets or sets the <see cref="PropertyBag"/> that holds the inheritable properties.
        /// </summary>
        [Browsable(false)]
        protected PropertyBag Properties
        {
            get { return properties; }
        }

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event.
        /// </summary>
        /// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        /// <summary>
        /// Registers the object.
        /// </summary>
        public virtual void RegisterObject()
        {
            FrameXmlDirectory.Default.Register(this);
        }

        /// <summary>
        /// Registers the object.
        /// </summary>
        /// <param name="oldName">The old name.</param>
        public virtual void RegisterObject(string oldName)
        {
            FrameXmlDirectory.Default.Register(this, oldName);
        }

        /// <summary>
        /// Unregisters the object.
        /// </summary>
        public virtual void UnregisterObject()
        {
            FrameXmlDirectory.Default.Unregister(this);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return this.Name;
        }
    }
}
